package com.dao;


import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.apache.commons.beanutils.BeanUtils;



public class BaseDao <T>{
	private String driverClass="oracle.jdbc.driver.OracleDriver";
	
	private String url="jdbc:oracle:thin:@192.168.199.247:1521:orcl";
	private String username="scott";
	private String password="123456";
	protected Connection conn=null;
	protected PreparedStatement pra=null;
	protected ResultSet rs=null;
	
	/**
	 * 获得连接对象
	 * @return	Connection
	 */
	public Connection getConnection(){
		try {
			Class.forName(driverClass);//将驱动加载到内存
			return DriverManager.getConnection(url, username, password);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}
	
	
	
	/**
	 * 更新方法：insert,update,delete
	 *    insert into 表名 values(?,?,?)
	 * @param sql
	 * @return
	 */
	public int executeUpdate(String sql,Object... paramsObjs){
		//1、获得连接对象
		Connection conn=this.getConnection();
		PreparedStatement pra=null;
		try {
			//2、获得预编译语句集
			pra=conn.prepareStatement(sql);
			
			//2.1、设置占位符的值ֵ
			if(paramsObjs!=null && paramsObjs.length>0){
				for(int i=0;i<paramsObjs.length;i++){
					pra.setObject(i+1, paramsObjs[i]);
				}
			}
			//3、执行sql语句
			return pra.executeUpdate();
		} catch (Exception e) {
			e.printStackTrace();
			throw new RuntimeException(e.getMessage());
		} finally{
			this.closeAll(null, pra, conn);
		}
	}

	
	/**
	 * 查询返回泛型集合列表
	 *   希望通过反射自动设值
	 *    1、列名和属性名一致（全小写）
	 *       属性名：empno==>列名：empno
	 *       问题：属性名：stuNo(java区分)==>列名：stuno(不区分大小写)
	 *       属性名：stuNo(java区分)==>列名：stu_no(不区分大小写，带下划线)   
	 *    2、当属性名和列名不对应时怎么通过反射解决这个问题
	 * @param sql         select语句
	 * @param columnsMap  列名与属性名的映射
	 * @param paramsObjs  占位符参数
	 * @return
	 */
	public List<T> executeQuery(String sql,Map<String, String> columnsMap,Class outputClass,Object... paramsObjs){
		List<T> list=new ArrayList<T>();//返回的结果集合
		//1、获得连接对象
		Connection conn=this.getConnection();
		PreparedStatement pra=null;
		ResultSet rs=null;
		try {
			//2、获得预编译语句集
			pra=conn.prepareStatement(sql);
			
			//2.1、设置占位符的值ֵ
			if(paramsObjs!=null && paramsObjs.length>0){
				for(int i=0;i<paramsObjs.length;i++){
					pra.setObject(i+1, paramsObjs[i]);
				}
			}
			
			//3、获得结果集并遍历
			rs=pra.executeQuery();
			while(rs.next()){
				list.add(this.getEntity(rs,columnsMap,outputClass));//通过调用子类的重写方法获得泛形类的实例
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally{
			this.closeAll(rs, pra, conn);
		}
		return list;
	}
	
	
	
	/**
	 * 根据outputClass返回指定泛型类的实例，将结果集rs中的数据设置到泛型类的实例中
	 *    1、列名和属性名一致（全小写）
	 *       属性名：empno==>列名：empno
	 *       问题：属性名：stuNo(java区分)==>列名：stuno(不区分大小写)
	 *       属性名：stuNo(java区分)==>列名：stu_no(不区分大小写，带下划线) (骆峰规则）  
	 *    2、当属性名和列名不对应时怎么通过反射解决这个问题 
	 * @param rs				结果集
	 * @param columnsMap		列名与属性名的映射：key:columnName,value:propertyName
	 * @param outputClass		返回的泛型类的Class
	 * @return
	 */
	public T getEntity(ResultSet rs,Map<String, String> columnsMap,Class outputClass){
		T resultObj=null;
		try {
			//1、获得返回的泛型类的实例对象
			resultObj=(T) outputClass.newInstance();//注意泛型类必须提供无参构造
			
			//2、取值设置到结果实例中
			ResultSetMetaData rsd= rs.getMetaData();//用于获得返回的结果的列名和总数
			// rsd.getColumnCount()：返回列的总数
			if(rsd!=null && rsd.getColumnCount()>0){
				for(int i=0;i<rsd.getColumnCount();i++){
					String columnName=rsd.getColumnName(i+1);//列名
					
					Field field=null;
					String propertyName=columnName.toLowerCase();//转成全小写
					//1、按照列名和属性名一致（全小写）
					try {
						field=outputClass.getDeclaredField(propertyName);
					} catch (Exception e) {}
					//2、按照骆峰规则
					if(field==null){
						StringBuffer propertyNameBf=new StringBuffer();
						//按骆峰规则拼接属性名
						String[] attrs=propertyName.split("_");
						if(attrs!=null && attrs.length>0){
							for(String str:attrs){
								if(propertyNameBf.length()>0){
									// menu_id==>str=id==>Id
									propertyNameBf.append(str.substring(0, 1).toUpperCase()+str.substring(1));
								}else{
									// menu_id==>str=menu
									propertyNameBf.append(str);
								}
							}
						}
						propertyName=propertyNameBf.toString();
						try {
							field=outputClass.getDeclaredField(propertyName);
						} catch (Exception e) {}
					}
					//3、如果列名与属性名通过上面二种情况还是获取不到时，就取传参设置的值
					if(field==null && columnsMap!=null){
						try {
							propertyName=columnsMap.get(columnName.toLowerCase());
							field=outputClass.getDeclaredField(propertyName);
						} catch (Exception e) {}
					}
					
					//找到了要到的泛型实例的属性
					if(field!=null){
						String fieldName=field.getName();
						//通过setXxx()方法将值设置到属性中去
//						String methodName="set"+fieldName.substring(0, 1)+fieldName.substring(1);
//						Method method=outputClass.getDeclaredMethod(methodName, field.getType());
//						if(method!=null){
//							method.invoke(resultObj, rs.getObject(columnName));//会有类型转换的错误。
//						}
						Object value=rs.getObject(columnName);
						if(value!=null){
							BeanUtils.copyProperty(resultObj, fieldName, value);
						}
						
					}
				}
			}
			
		} catch (Exception e) {
			e.printStackTrace();
		}
		return resultObj;
	}
	
	
	
	
	/**
	 * 关闭资源的方法
	 *    从内往外的关闭：先关ResultSet，再关Statement，最后是Connection   
	 * @param rs
	 * @param sta
	 * @param conn
	 */
	public void closeAll(ResultSet rs,Statement sta,Connection conn){
		try {
			if(rs!=null) rs.close();
			if(sta!=null) sta.close();
			if(conn!=null) conn.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}
